/*
 * Copyright 2011 Focus Technology, Co., Ltd. All rights reserved.
 */
package com.tfc.analysis;

import com.tfc.analysis.entity.Keyword;
import com.tfc.analysis.fragment.AbstractFragment;
import com.tfc.analysis.fragment.HTMLFragment;
import com.tfc.analysis.process.BaseFinderProcess;
import com.tfc.analysis.process.DataInit;
import com.tfc.analysis.process.Highlight;
import com.tfc.analysis.process.WordFinder;
import com.tfc.analysis.utils.StringUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 关键词快速查找，即：从指定的文本中快速查找出包含有指定词库中的词。<br>
 * 算法简介：<br>
 * 初始化时将所有关键词聚合构造成一棵棵的关键词树（建树相当于建索引），在查询关键词时，对文本中每个字进行树查询（树查询的效率是很高的）。<br>
 * 注意：不要在使用过程中使用除analysis以外的方法！
 *
 * @author taofucheng
 */
@SuppressWarnings({"rawtypes"})
public class KWSeeker {
    /**
     * 所有的关键词。
     */
    private Set<Keyword> sensitiveWords = new HashSet<Keyword>();
    /**
     * 关键词树。第一级是模块分类
     */
    private Map<String, Map> sensitiveWordsTree = new ConcurrentHashMap<String, Map>();
    /**
     * 最短的关键词长度。用于对短于这个长度的文本不处理的判断，以节省一定的效率
     */
    private int sensitiveWordMinLen = 0;

    /**
     * 白名单的关键词
     */
    private Set<Keyword> whiteWords = new HashSet<Keyword>();
    /**
     * 每个词对应的白名单
     */
    private Map<Keyword, Set<Keyword>> wordToWhite = new HashMap<Keyword, Set<Keyword>>();
    /**
     * 最短的关键词长度。用于对短于这个长度的文本不处理的判断，以节省一定的效率
     */
    private int whiteWordMaxLen = 0;

    /**
     * 指定哪些文字或组合，在匹配过程中被忽略，如：&lt;em&gt;
     */
    private Set<Keyword> ignores = new HashSet<Keyword>();
    /**
     * 关键词树。第一级是模块分类
     */
    private Map<String, Map> ignoreTree = new ConcurrentHashMap<String, Map>();
    private int ignoreWordMaxLen = 0;

    private KWSeeker() {
        // 不对外开放
    }

    /**
     * 获取一个实例
     *
     * @return
     */
    public static KWSeeker getInstance(List<Keyword> newWords) {
        KWSeeker sw = new KWSeeker();
        sw.addSensitiveWord(newWords.toArray(new Keyword[]{}));
        return sw;
    }

    /**
     * 获取一个实例
     *
     * @return
     */
    public static KWSeeker getInstance(Keyword... newWords) {
        KWSeeker sw = new KWSeeker();
        sw.addSensitiveWord(newWords);
        return sw;
    }

    /**
     * 重置白名单词
     *
     * @param newWords
     */
    public KWSeeker setWhiteWords(List<Keyword> newWords) {
        return setWhiteWords(newWords.toArray(new Keyword[]{}));
    }

    /**
     * 重置白名单词
     *
     * @param newWords
     */
    public KWSeeker setWhiteWords(Keyword... newWords) {
        whiteWords.clear();
        return addWhiteWords(newWords);
    }

    /**
     * 添加白名单词
     *
     * @param newWords
     */
    public KWSeeker addWhiteWords(List<Keyword> newWords) {
        return addWhiteWords(newWords.toArray(new Keyword[]{}));
    }

    /**
     * 添加白名单词
     *
     * @param newWords
     */
    public KWSeeker addWhiteWords(Keyword... newWords) {
        boolean proceed = false;
        if (newWords != null && newWords.length > 0) {
            for (Keyword sw : newWords) {
                if (StringUtils.isNotEmpty(sw.getWord())) {
                    whiteWords.add(sw);
                    proceed = true;
                }
            }
        }
        if (proceed) {
            initWordsTree();
        }
        return this;
    }

    /**
     * 添加白名单词
     *
     * @param newWords
     */
    public KWSeeker addWhiteWords(String... newWords) {
        boolean proceed = false;
        if (newWords != null && newWords.length > 0) {
            for (String sw : newWords) {
                if (StringUtils.isNotEmpty(sw)) {
                    whiteWords.add(new Keyword(sw));
                    proceed = true;
                }
            }
        }
        if (proceed) {
            initWordsTree();
        }
        return this;
    }

    private void initWordsTree() {
        Map<Integer, Integer> result = DataInit.generalWhiteWordsTree(sensitiveWords, sensitiveWordsTree, whiteWords, wordToWhite);
        if (result != null && result.size() > 0) {
            sensitiveWordMinLen = result.keySet().iterator().next();
            whiteWordMaxLen = result.values().iterator().next();
        }
    }

    /**
     * 添加一个或多个新的关键词。先对每个词进行去除前后空格，然后，如果有为null或""的元素，将不会被添加进去，其它才能被正常添加进词库。
     *
     * @param newWord 新的关键词。可以是若干个字符串，也可以是字符串数组。
     */
    public KWSeeker addSensitiveWord(String... newWord) {
        if (newWord != null && newWord.length > 0) {
            for (String sw : newWord) {
                if (StringUtils.isNotEmpty(sw)) {
                    sensitiveWords.add(new Keyword(sw));
                }
            }
            initWordsTree();
        }
        return this;
    }

    /**
     * 添加一个或多个新的关键词。先对每个词进行去除前后空格，然后，如果有为null或""的元素，将不会被添加进去，其它才能被正常添加进词库。
     *
     * @param newWord 新的关键词。可以是若干个字符串，也可以是字符串数组。
     */
    public KWSeeker addSensitiveWord(Keyword... newWord) {
        if (newWord != null && newWord.length > 0) {
            for (Keyword sw : newWord) {
                if (StringUtils.isNotEmpty(sw.getWord())) {
                    sensitiveWords.add(sw);
                }
            }
            initWordsTree();
        }
        return this;
    }

    /**
     * 清除所有的关键词
     */
    public void clear() {
        sensitiveWords.clear();
        whiteWords.clear();
        sensitiveWordsTree.clear();
        wordToWhite.clear();
    }

    /**
     * 添加匹配时需要忽略的内容，如：指定"&lt;em&gt;"，则“这是&lt;em&gt;字体试验”在匹配时会被视为“这是字体试验”进行匹配操作，但该内容在结果上会被保留
     *
     * @param ignores
     */
    public KWSeeker addIgnores(String... ignores) {
        if (ignores == null || ignores.length < 1) {
            return this;
        }
        for (String ignore : ignores) {
            if (StringUtils.isEmpty(ignore)) {
                continue;
            }
            //这个地方不能进行trim，因为可能忽略的内容就是前后有空格
            this.ignores.add(new Keyword(ignore));
        }
        Map<Integer, Integer> result = DataInit.generalWordTree(this.ignores, ignoreTree);
        if (result != null && result.size() > 0) {
            ignoreWordMaxLen = result.values().iterator().next();
        }
        return this;
    }

    public KWSeeker removeIgnores(String... ignores) {
        if (ignores == null || ignores.length < 1) {
            return this;
        }
        for (String ignore : ignores) {
            if (StringUtils.isEmpty(ignore)) {
                continue;
            }
            //这个地方不能进行trim，因为可能忽略的内容就是前后有空格
            this.ignores.remove(new Keyword(ignore));
        }
        Map<Integer, Integer> result = DataInit.generalWordTree(this.ignores, ignoreTree);
        if (result != null && result.size() > 0) {
            ignoreWordMaxLen = result.values().iterator().next();
        }
        return this;
    }

    /**
     * 是否包含指定的词
     *
     * @param word 指定的关键词
     * @return true:包含；false:不包含
     */
    public boolean contains(String word) {
        if (sensitiveWords.isEmpty() || StringUtils.isEmpty(word)) {
            return false;
        }
        for (Keyword w : sensitiveWords) {
            if (word.equals(w.getWord())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 含有敏感的个数
     *
     * @return 返回具体的数量
     */
    public int sesitiveSize() {
        return sensitiveWords.size();
    }

    /**
     * 从词库中移除指定的关键词
     *
     * @param word 指定需要被移除的关键词
     * @return true:移除成功；false:移除失败
     */
    public boolean removeSensitiveWord(String word) {
        return removeWord(word, sensitiveWords);
    }

    /**
     * 从词库中移除指定的关键词
     *
     * @param word 指定需要被移除的关键词
     * @return true:移除成功；false:移除失败
     */
    public boolean removeWhiteWord(String word) {
        return removeWord(word, whiteWords);
    }

    private boolean removeWord(String word, Set<Keyword> words) {
        if (words.isEmpty() || StringUtils.isEmpty(word)) {
            return false;
        }
        Set<Keyword> ws = new HashSet<Keyword>();
        for (Keyword w : words) {
            if (word.equals(w.getWord())) {
                ws.add(w);
            }
        }
        // 以上不直接remove的原因是：会对循环造成问题，引起bug。
        if (!ws.isEmpty()) {
            for (Keyword w : ws) {
                words.remove(w);
            }
            initWordsTree();
            return true;
        }
        return false;
    }

    /**
     * 返回将传入的字符串使用默认的高亮方式（即HTML方式）将关键词标记出来的结果
     *
     * @param text 传入需要处理的字符串。
     * @return 返回处理后的结果。
     */
    public String highlight(String text) {
        return highlight(text, new HTMLFragment("<font color='red'>", "</font>"));
    }

    /**
     * 返回将传入的字符串使用指定的高亮方式将关键词标记出来的结果。如果没有指定formatter或text，则直接返回text。
     * 这里使用指定的关键词词库进行处理。
     *
     * @param text     传入需要处理的字符串。
     * @param fragment 指定的高亮方式
     * @return 返回处理后的结果。
     */
    public String highlight(String text, AbstractFragment fragment) {
        return (String) process(new Highlight(), text, fragment);
    }

    /**
     * 将指定的字符串中的关键词提取出来。
     *
     * @param text 指定的字符串。即：预处理的字符串
     * @return 返回其中所有关键词。如果没有，则返回null。
     */
    @SuppressWarnings("unchecked")
    public Set<String> findWords(String text) {
        return (Set<String>) process(new WordFinder(), text, null);
    }

    /**
     * 使用指定的处理器进行处理！
     *
     * @param proc     处理器
     * @param text     目标文本
     * @param fragment 命中词的处理器
     * @return 返回处理结果
     */
    public Object process(BaseFinderProcess proc, String text, AbstractFragment fragment) {
        proc.setWhiteWordMaxLen(whiteWordMaxLen);
        proc.setWordToWhite(wordToWhite);
        proc.setIgnoreTree(ignoreTree);
        proc.setIgnoreWordMaxLen(ignoreWordMaxLen);

        return proc.process(sensitiveWordsTree, text, fragment, sensitiveWordMinLen);
    }
}
